using System;
using SharpMud.Net.Telnet.IAC;

namespace SharpMud.Net.Telnet.Session.DataProcessors.IAC
{
	public class InputLayer : System.IO.IDataProcessor
	{	
		private System.Collections.IQueue<byte>	_ParsedDataQueue;
		
		private System.Collections.IQueue<byte>	_UnparsedDataQueue;
		private IACTelnetLayer								_HostLayer;

		private System.Collections.IQueue<byte>	_SubrequestQueue;
		private ControlCode									_SubrequestType;

		#region Constructors
		private InputLayer()
		{
		}

		internal InputLayer(IACTelnetLayer hostLayer)
		{
			_HostLayer = hostLayer;
		}
		#endregion

		#region Public Properties
		public IACTelnetLayer HostLayer
		{
			get
			{
				return _HostLayer;
			}
		}
		public Net.Telnet.Session.TelnetSessionBase HostSession
		{
			get
			{
				return HostLayer.HostSession;
			}
		}
		#endregion

		#region IDataProcessor Members

		public System.Collections.IQueue<byte> OutputQueue
		{
			get
			{
				return this._ParsedDataQueue;
			}
		}

		public System.Collections.IQueue<byte> InputQueue
		{
			get
			{
				return this._UnparsedDataQueue;
			}
		}

		public void Initialize(System.Collections.IQueue<byte> inputQueue, System.Collections.IQueue<byte> outputQueue)
		{
			this._UnparsedDataQueue = inputQueue;
			this._ParsedDataQueue = outputQueue;
		}

		public void Pulse()
		{
			if(this._SubrequestQueue==null)
			{
				//We know that we aren't in an IAC subrequest
				if(this._UnparsedDataQueue.Count<=0) return;
				if(this._UnparsedDataQueue.Peek()==(byte)ControlCodes.IAC && this._UnparsedDataQueue.Count>=2)
				{
					byte secondByte = _UnparsedDataQueue.Peek(2)[1];
					//Check and see if we are dealing with a simple IAC escape
					if(secondByte==(byte)ControlCodes.IAC)
					{
						this._ParsedDataQueue.Enqueue(_UnparsedDataQueue.Dequeue(2)[0]);
						return;
					}
					//Check and see if we are dealing with a subrequest header
					if(secondByte==(byte)ControlCodes.SB)
					{
						//See if we have the needed third byte of the subrequest header
						if(this._UnparsedDataQueue.Count>=3)
						{
							//We have a subrequest. Start up subrequest piping.
							byte thirdByte = this._UnparsedDataQueue.Dequeue(3)[2];
							this._SubrequestQueue = new System.Collections.GenericQueue<byte>();
							this._SubrequestType = (ControlCode)thirdByte;
							return;
						}
						else
							return;	  //We dont have the third byte, just wait for it
					}
					//Check and see if the second byte indicates a feature negotiation or something else
					if(		(secondByte==(byte)ControlCodes.DO)		||
						(secondByte==(byte)ControlCodes.DONT)	||
						(secondByte==(byte)ControlCodes.WILL)	||
						(secondByte==(byte)ControlCodes.WONT)	)
					{
						//If we get to here then we are dealing with a feature negotiation
						if(_UnparsedDataQueue.Count>=3)
						{
							byte firstByte = this._UnparsedDataQueue.Dequeue();
							secondByte = this._UnparsedDataQueue.Dequeue();
							byte thirdByte = this._UnparsedDataQueue.Dequeue();
							if(this.IAC_Feature_Negotiation!=null)
							{
								this.IAC_Feature_Negotiation(this,new IACInputEventArgs((ControlCode)secondByte,(ControlCode)thirdByte));
							}
							return;
						}
						else
						{
							//Not enough bytes yet to know what FEATURE to NEGOTIATE
							return;
						}
					}
					else
					{
						//If we get here then this is an 'other' sequence
						secondByte = this._UnparsedDataQueue.Dequeue();
						if(this.IAC_Other!=null)
						{
							this.IAC_Other(this,new IACInputEventArgs(secondByte));
						}
						return;
					}
				}
				else
				{
					//No IAC, no subrequests, nothing.
					//This is regular data. Pass it along.
					this._ParsedDataQueue.Enqueue(this._UnparsedDataQueue.Dequeue());
					return;
				}
			}
			else
			{
				//We are in a subrequest.
				if(this._UnparsedDataQueue.Peek()!=(byte)ControlCodes.IAC)
				{
					//Normal byte, pass it into subrequest queue
					this._SubrequestQueue.Enqueue(this._UnparsedDataQueue.Dequeue());
					return;
				}
				else if(this._UnparsedDataQueue.Count>=2)
				{
					byte firstByte = this._UnparsedDataQueue.Dequeue();
					//We have an IAC byte, now lets see what it has to say
					byte secondByte = this._UnparsedDataQueue.Dequeue();
					if(secondByte==(byte)ControlCodes.IAC)
					{
						//Its an IAC escape. Big deal.
						this._SubrequestQueue.Enqueue(secondByte);
						return;
					}
					else if(secondByte==(byte)ControlCodes.SE)
					{
						//The subrequest is over! Oh no!
						if(this.IAC_Subrequest!=null)
						{
							this.IAC_Subrequest(this,new IACInputEventArgs(this._SubrequestType,this._SubrequestQueue.Dequeue(this._SubrequestQueue.Count)));
						}
						this._SubrequestQueue = null;
						this._SubrequestType = null;
					}
					else
					{
						//What the hell!! We shouldn't get here. Not at all.
						System.Diagnostics.Debugger.Break();
					}
				}
			}
		}

		#endregion

		public event IACInputEventHandler IAC_Subrequest;
		public event IACInputEventHandler IAC_Other;
		public event IACInputEventHandler IAC_Feature_Negotiation;

        #region IDisposable Members

        public void Dispose()
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }

	public delegate void IACInputEventHandler(IAC.InputLayer sender, IACInputEventArgs e);

	public class IACInputEventArgs : System.EventArgs
	{
		private IACSequenceType _Type;

		private byte			_Other;
		private ControlCode		_NegotiationQuery;
		private ControlCode		_FeatureType;
		private byte[]			_SubrequestData;
		
		public IACSequenceType SequenceType
		{
			get
			{
				return this._Type;
			}
		}

		public byte			Other				{get{return _Other;}}
		public ControlCode	NegotiationQuery	{get{return _NegotiationQuery;}}
		public ControlCode	FeatureType			{get{return _FeatureType;}}
		public byte[]		SubrequestData		{get{return _SubrequestData;}}

		private IACInputEventArgs()
		{
		}
		internal IACInputEventArgs(byte other)
		{
			this._Type = IACSequenceType.IAC_Other;
			this._Other = other;
			_SubrequestData = new byte[] {};
		}
		internal IACInputEventArgs(ControlCode negotiationQuery, ControlCode featureType)
		{
			if(negotiationQuery==null) throw new ArgumentNullException("negotiationQuery");
			if(featureType==null) throw new ArgumentException("featureType");

			this._Type = IACSequenceType.IAC_Feature_Negotiation;
			this._NegotiationQuery = negotiationQuery;
			this._FeatureType = featureType;
			_SubrequestData = new byte[] {};
		}
		internal IACInputEventArgs(ControlCode featureType,byte[] subrequestData)
		{
			if(featureType==null) throw new ArgumentNullException("featureType");
			if(subrequestData==null) throw new ArgumentNullException("subrequestData");

			this._Type = IACSequenceType.IAC_Subrequest;
			this._FeatureType = featureType;
			this._SubrequestData = subrequestData;
		}
	}

	public enum IACSequenceType
	{
		IAC_Other,
		IAC_Feature_Negotiation,
		IAC_Subrequest
	}
}